import asyncio
import base64
import ctypes
import glob
import importlib
import os
import re
import struct
import time
import traceback
from asyncio import current_task, Event, Queue
from copy import copy
from datetime import timedelta as td
from functools import lru_cache

import aioftp
import async_timeout
import tftpy
from google.protobuf.descriptor import FieldDescriptor
from google.protobuf.json_format import MessageToDict
from loguru import logger
from opentelemetry import trace

from proto.firmata_pb2 import pb_filelist, pb_serial, pb_cmd, pb_thread_list, pb_cmd_type, pb_response, pb_object, \
    pb_soem_cmd, FM_SOEM, REPORT_PLC_MD5, CB_GET_LOG_NUMBER, CB_GET_RTC, CB_SET_IP, CB_SET_V, CB_SET_FORCE, \
    CB_SET_PLC_FILE, CB_GET_V, CB_GET_RTE_VERSION, CB_SET_RTC, FM_GET_TASK_NRS, FM_GET_TASK_NAME, FM_PUT_DATA_BLOCK, \
    FM_GET_DBG, FM_GET_LOC_TAB, FM_READ_VALUE_REP, FM_GET_PLC_INFO, CB_THREAD_INFO, CB_GET_REMAIN_MEM, \
    CB_GET_BOOT_VERSION, CB_CPU_USAGE, FM_WRITE_VALUE_REP, FM_REMOVE_FILE, CB_CLEAR_V, FM_GET_PLC_STATE, \
    FM_GET_TASK_DETAIL, CB_RM_KEY, CB_WRITE_KEY, FM_READ_KEY_BYTES, FM_WRITE_KEY_BYTES, CB_SET_TSL_RANGE, \
    CB_SET_TSL_STATUS, CB_READ_KEY, FM_GET_DATA_BLOCK, FM_READ_VALUE, CB_TSL_CLEAR, FM_LIST_KEY, FM_LFS_LS, \
    FM_GET_SERIAL_INFO, FM_PROTOBUF, FM_SET_LOCATION, CB_GET_TSL_BY_ID, FM_WRITE_VALUE, FM_GET_LOCATION, \
    pb_soem_cmd_type
from pymata_express.pymata_express.private_constants import PrivateConstants
from pymata_express.pymata_express.pymata_express import PymataExpress
from tracing import trace_function

loop = asyncio.get_event_loop()
MCU_FLASH = 0
LFS = 1
PROG_FLASH = 2
MEM_REGION = ['MCU_FLASH', 'LFS', 'PROG_FLASH']
# ctypes.pythonapi.PyUnicode_AsUTF8.argtypes = (ctypes.c_void_p,)
# ctypes.pythonapi.PyUnicode_AsUTF8.restype = ctypes.POINTER(ctypes.c_char)

VAR_REGION = [
    REGION_NULL,
    REGION_XI,
    REGION_YI,
    REGION_XA,
    REGION_YA,
    REGION_DIGITAL,
    REGION_SYS,
    REGION_16,
    REGION_32,
    REGION_INFO,
    REGION_CONFIG,
    REGION_HOLDER
] = range(12)

error_code = ['CRC错误',
              'DEV_IS_OPEN',
              'NO_DEVICE',
              'DEVICE_SHUTDOWN_ERR',
              'DEVICE_WRITE_ERR',
              'NODB',
              'TIMEOUT',
              'NO_OBJECT',
              'RANGE_OV',
              'LENGTH_OV',

              'ETH_INIT_ERR',
              'DISPLAY_ERR',
              'FILE_NOT_EXIST',
              'KV_VALUE_ILLEAGAL',
              'KV_NAME_ILLEAGL']

fdb_tsl_status = [
    FDB_TSL_UNUSED,
    FDB_TSL_PRE_WRITE,
    FDB_TSL_WRITE,
    FDB_TSL_USER_STATUS1,
    FDB_TSL_DELETED,
    FDB_TSL_USER_STATUS2,
] = range(0x0, 6)

kv_type = [
    KV_BIN_VALUE,
    KV_STR_VALUE,
    KV_KEY_UDINT
] = range(0x0, 3)
types = ["REAL", "LREAL", "SINT", "INT", "DINT", "LINT", "USINT", "UINT", "UDINT", "ULINT", "TIME", "BYTE",
         "WORD", "DWORD", "LWORD", "BOOL", "STRING", "DATE", "TOD", "DT",
         "REAL_P", "LREAL_P", "SINT_P", "INT_P", "DINT_P", "LINT_P", "USINT_P", "UINT_P", "UDINT_P", "ULINT_P",
         "TIME_P", "BYTE_P",
         "WORD_P", "DWORD_P", "LWORD_P", "BOOL_P", "STRING_P", "DATE_P", "TOD_P", "DT_P",
         "REAL_O", "LREAL_O", "SINT_O", "INT_O", "DINT_O", "LINT_O", "USINT_O", "UINT_O", "UDINT_O", "ULINT_O",
         "TIME_O", "BYTE_O",
         "WORD_O", "DWORD_O", "LWORD_O", "BOOL_O", "STRING_O", "DATE_O", "TOD_O", "DT_O",
         ]
types_c = {"float": 0, "double": 1, "char": 2, "short": 3, "int": 4, "long": 5, "unsigned char": 6, "unsigned short": 7,
           "unsigned int": 8,
           "unsigned long": 9, "time": 10, "byte": 11, "word": 12, "dword": 13, "lword": 14, "bool": 15, "string": 16,
           "date": 17, "tod": 18, "dt": 19}


def find_paths_with_prefix(directory, prefix):
    # 使用glob.iglob函数和re.IGNORECASE标志查找指定开头的路径（不区分大小写）
    pattern = re.compile(re.escape(prefix), re.IGNORECASE)
    paths = [path for path in glob.iglob(directory + '/*') if pattern.match(os.path.basename(path))]
    return paths


@lru_cache()
def load_proto(model, major=0, minor=0, build=0, message_name='pb_object'):
    # 加载proto文件
    pd = find_paths_with_prefix(os.path.join(os.path.dirname(__file__), '..', 'product'),
                                f'{model}_rte_{major}.{minor}.{build}')
    if len(pd) == 0 or not os.path.exists(pd[0]):
        raise FileNotFoundError(f'proto file not found: {model}_rte_{major}.{minor}.{build}')
    proto_file = os.path.realpath(os.path.join(pd[0], 'proto', 'plc_var_pb2.py'))
    if not os.path.exists(proto_file):
        raise FileNotFoundError(f'proto file not found: {proto_file}')
    spec = importlib.util.spec_from_file_location("plc_var_pb2", proto_file)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module.__dict__[message_name]


@lru_cache()
def pb_holder(model, major=0, minor=0, build=0):
    proto = load_proto(model, major, minor, build, 'pb_rte_holder')
    return proto()


@lru_cache()
def pb_var(model, major=0, minor=0, build=0):
    proto = load_proto(model, major, minor, build, 'pb_rte_data')
    return proto()


def get_tag_by_field_name(message_type, field_name):
    for field_descriptor in message_type.DESCRIPTOR.fields:
        if field_descriptor.name == field_name:
            return field_descriptor.number, field_descriptor.type
    return None, None


def encode_protobuf_data(field_type, value):
    if field_type == FieldDescriptor.TYPE_DOUBLE:
        data = struct.pack('<d', float(value))
    elif field_type == FieldDescriptor.TYPE_FLOAT:
        data = struct.pack('<f', float(value))
    elif field_type == FieldDescriptor.TYPE_INT64:
        data = int.to_bytes(int(value), 8, 'little', signed=True)
    elif field_type == FieldDescriptor.TYPE_UINT64:
        data = int.to_bytes(int(value), 8, 'little', signed=False)
    elif field_type == FieldDescriptor.TYPE_INT32:
        data = int.to_bytes(int(value), 4, 'little', signed=True)
    elif field_type == FieldDescriptor.TYPE_FIXED64:
        data = int.to_bytes(int(value), 8, 'little', signed=False)
    elif field_type == FieldDescriptor.TYPE_FIXED32:
        data = int.to_bytes(int(value), 4, 'little', signed=False)
    elif field_type == FieldDescriptor.TYPE_BOOL:
        data = bool(value)
    elif field_type == FieldDescriptor.TYPE_STRING:
        data = value.encode()
    elif field_type == FieldDescriptor.TYPE_GROUP:
        return False
    elif field_type == FieldDescriptor.TYPE_MESSAGE:
        return False
    elif field_type == FieldDescriptor.TYPE_BYTES:
        data = value.encode()
    elif field_type == FieldDescriptor.TYPE_UINT32:
        data = int.to_bytes(int(value), 4, 'little', signed=False)
    elif field_type == FieldDescriptor.TYPE_ENUM:
        data = int.to_bytes(int(value), 4, 'little', signed=False)
    elif field_type == FieldDescriptor.TYPE_SFIXED32:
        data = int.to_bytes(int(value), 4, 'little', signed=False)
    elif field_type == FieldDescriptor.TYPE_SFIXED64:
        data = int.to_bytes(int(value), 8, 'little', signed=False)
    elif field_type == FieldDescriptor.TYPE_SINT32:
        data = int.to_bytes(int(value), 4, 'little', signed=False)
    elif field_type == FieldDescriptor.TYPE_SINT64:
        data = int.to_bytes(int(value), 8, 'little', signed=False)
    else:
        return False
    return data


def get_protobuf_field_type_name(field_type):
    from google.protobuf.descriptor import FieldDescriptor
    # 根据字段类型值获取字段类型名称
    if field_type == FieldDescriptor.TYPE_DOUBLE:
        return 'double'
    elif field_type == FieldDescriptor.TYPE_FLOAT:
        return 'float'
    elif field_type == FieldDescriptor.TYPE_INT64:
        return 'int64'
    elif field_type == FieldDescriptor.TYPE_UINT64:
        return 'uint64'
    elif field_type == FieldDescriptor.TYPE_INT32:
        return 'int32'
    elif field_type == FieldDescriptor.TYPE_FIXED64:
        return 'fixed64'
    elif field_type == FieldDescriptor.TYPE_FIXED32:
        return 'fixed32'
    elif field_type == FieldDescriptor.TYPE_BOOL:
        return 'bool'
    elif field_type == FieldDescriptor.TYPE_STRING:
        return 'string'
    elif field_type == FieldDescriptor.TYPE_GROUP:
        return 'group'
    elif field_type == FieldDescriptor.TYPE_MESSAGE:
        return 'message'
    elif field_type == FieldDescriptor.TYPE_BYTES:
        return 'bytes'
    elif field_type == FieldDescriptor.TYPE_UINT32:
        return 'uint32'
    elif field_type == FieldDescriptor.TYPE_ENUM:
        return 'enum'
    elif field_type == FieldDescriptor.TYPE_SFIXED32:
        return 'sfixed32'
    elif field_type == FieldDescriptor.TYPE_SFIXED64:
        return 'sfixed64'
    elif field_type == FieldDescriptor.TYPE_SINT32:
        return 'sint32'
    elif field_type == FieldDescriptor.TYPE_SINT64:
        return 'sint64'
    else:
        return 'unknown'


class IEC_STRING(ctypes.Structure):
    """Must be changed according to changes in iec_types.h"""
    _fields_ = [("len", ctypes.c_uint8),
                ("body", ctypes.c_char * 126)]


class IEC_TIME(ctypes.Structure):
    """Must be changed according to changes in iec_types.h"""
    _fields_ = [("s", ctypes.c_long),  # tv_sec
                ("ns", ctypes.c_long)]  # tv_nsec


def _t(t, u=lambda x: x.value, p=lambda t, x: t(x)):
    """
    Args:
        t:
        u:
        p:
    """
    return t, u, p


def _ttime():
    return (IEC_TIME,
            lambda x: td(0, x.s, x.ns / 1000.0),
            lambda t, x: t(x.days * 24 * 3600 + x.seconds, x.microseconds * 1000))


SameEndianessTypeTranslator = {
    "BOOL": _t(ctypes.c_uint8, lambda x: x.value != 0),
    "STEP": _t(ctypes.c_uint8),
    "TRANSITION": _t(ctypes.c_uint8),
    "ACTION": _t(ctypes.c_uint8),
    "SINT": _t(ctypes.c_int8),
    "USINT": _t(ctypes.c_uint8),
    "BYTE": _t(ctypes.c_uint8),
    "STRING": (IEC_STRING,
               lambda x: x.body[:x.len],
               lambda t, x: t(len(x), x)),
    "INT": _t(ctypes.c_int16),
    "UINT": _t(ctypes.c_uint16),
    "WORD": _t(ctypes.c_uint16),
    "DINT": _t(ctypes.c_int32),
    "UDINT": _t(ctypes.c_uint32),
    "DWORD": _t(ctypes.c_uint32),
    "LINT": _t(ctypes.c_int64),
    "ULINT": _t(ctypes.c_uint64),
    "LWORD": _t(ctypes.c_uint64),
    "REAL": _t(ctypes.c_float),
    "LREAL": _t(ctypes.c_double),
    "TIME": _ttime(),
    "TOD": _ttime(),
    "DATE": _ttime(),
    "DT": _ttime(),
}
TypeTranslator = SameEndianessTypeTranslator
lock = asyncio.Lock()
varLock = asyncio.Lock()
reportLock = Event()


def two_byte_to_byte(bts):
    bits = 7
    i = 0
    res = []
    while i < len(bts) - 1:
        a = bts[i] >> (7 - bits)
        a |= (bts[i + 1] << bits) & 0xff
        res.append(a)
        i += 2
    return res


def get_value_by_type(value, typ):
    if typ == 1:
        if len(value) != 8:
            logger.error("value len error %s" % value)
            return 0.0
        return struct.unpack('lf', bytes(value))[0]
    elif typ == 2:
        return value[0]
    elif typ == 3:
        return int.from_bytes(value, 'little', signed=True)
    elif typ == 4:
        return int.from_bytes(value, 'little', signed=True)
    elif typ == 7:
        return int.from_bytes(value, 'little', signed=False)
    elif typ == 8:
        return int.from_bytes(value, 'little', signed=False)
    elif typ == 0 or typ == 'REAL_P':
        if len(value) != 4:
            logger.error("value len error %s" % value)
            return 0.0
        return struct.unpack('f', bytes(value))[0]
    elif typ == 6:  # BOOL,按字节操作
        return value[0]
    elif typ == 16:
        return value.decode('utf-8')
    elif typ == 11:  # byte
        return value[0]
    elif typ == 15:
        return value[0]
    else:
        raise Exception(f"unknown type {typ}")


class FIRMATA:

    def __init__(self, confnodesroot, _URL, dtr=None, rts=None, arduino_wait=3, evt_loop=loop):
        """
        Args:
            confnodesroot:
            _URL:
        """
        self.plc_var_reading = None
        self.confnodesroot = confnodesroot
        comport = None
        self.ip = None
        port = None
        if _URL:
            if _URL.startswith('ble'):
                comport = _URL
            else:
                s = _URL.split(':')
                if len(s) == 2 and s[1] != '0':
                    self.ip = s[0]
                    port = int(s[1])
                elif len(s) == 7:
                    self.ip = ':'.join(s[0:-1])
                    port = int(s[-1])
                else:
                    comport = s[0]
        self.board = PymataExpress(com_port=comport, ip_address=self.ip, ip_port=port, shutdown_on_exception=True,
                                   dtr=dtr, rts=rts, arduino_wait=arduino_wait, loop=evt_loop,
                                   autostart=False, close_loop_on_shutdown=False)
        self.PLCStatus = None
        self.plc_var = {}
        self.plc_vars = {}
        self.plc_var_changed = False
        self.sn = None
        self.features = None
        self.report_analog_pins = []
        self.kv: dict = {}  # 键值表
        self.region = {}
        self.model = None
        self.rte_const = None

    async def init(self):
        logger.debug("firmata init")
        await self.board.start_aio()
        logger.debug("firmata init")
        return True

    def __getattr__(self, attrName):
        if object.__getattribute__(self, "board") is not None:
            atr = getattr(self.board, attrName)
            return atr
        raise AttributeError("Unknown Attribute `" + attrName + "`")

    def close(self):
        logger.debug("shutdown socket")
        self.board.loop.run_until_complete(self.board.shutdown())

    async def close_async(self):

        await self.board.shutdown()

    async def wait(self, cmd, timeout=0.5) -> []:
        current_time = time.time()
        task = asyncio.all_tasks()
        # for t in task:
        #     if t._coro.cr_code.co_name == "_arduino_report_dispatcher":
        #         logger.debug(t._state)
        self.board.query_reply_data[cmd] = None
        while self.board.query_reply_data.get(cmd) is None:
            elapsed_time = time.time()
            if elapsed_time - current_time > timeout:
                # task = asyncio.Task.all_tasks()
                # for t in task:
                #     if t._coro.cr_code.co_name == "_arduino_report_dispatcher":
                #         logger.debug(t._state)
                raise TimeoutError('no response %s' % current_task().get_name())
            await asyncio.sleep(self.board.sleep_tune)
        return self.board.query_reply_data.get(cmd)

    async def restartPLC(self):
        async with lock:
            sub_buf = await self.board.send_reset()
            return sub_buf == b'OK'

    async def MatchMD5(self, MD5):
        """
        Args:
            MD5:
        """
        async with lock:
            async def get_md5(byte):
                b = byte
                string_ints = [chr(innate) for innate in b]
                str_of_ints = "".join(string_ints)
                self.board.query_reply_data[REPORT_PLC_MD5] = str_of_ints

            self.board.command_dictionary.update({REPORT_PLC_MD5: get_md5})
            await self.board._send_sysex(REPORT_PLC_MD5)
            selfMD5 = await self.wait(REPORT_PLC_MD5)
            if selfMD5 == MD5:
                return True
            logger.debug("local %s & remote %s" % (MD5, selfMD5))
            return False

    async def Get_PLC_status(self):
        async with lock:
            self.PLCStatus = None
            refcounts = [0, 0, 0, 0]

            async def get_state(_byte):
                self.board.query_reply_data[CB_GET_LOG_NUMBER] = _byte

            self.board.command_dictionary.update({CB_GET_LOG_NUMBER: get_state})
            await self.board._send_sysex(CB_GET_LOG_NUMBER)
            byte = await self.wait(CB_GET_LOG_NUMBER)
            if byte:
                self.PLCStatus = byte[0]
                refcounts[0] = byte[1]
                refcounts[1] = byte[2]
                refcounts[2] = byte[3]
                refcounts[3] = byte[4]
            if not self.PLCStatus:
                raise Exception('no response')
            return self.PLCStatus, refcounts

    async def GetRteVer(self):
        async with lock:
            async def get_state(data):
                sn = int.from_bytes(data[0:3], 'little')
                major = int.from_bytes(data[4:7], 'little')
                minor = int.from_bytes(data[8:11], 'little')
                build = int.from_bytes(data[12:15], 'little')
                features = int.from_bytes(data[16:19], 'little')
                self.sn = str(hex(sn))
                self.features = features
                self.board.query_reply_data[CB_GET_RTE_VERSION] = [sn, major, minor, build, features, 0]
                logger.debug(self.board.query_reply_data[CB_GET_RTE_VERSION])

            self.board.command_dictionary.update({CB_GET_RTE_VERSION: get_state})
            await self.board._send_sysex(CB_GET_RTE_VERSION)
            refcounts = await self.wait(CB_GET_RTE_VERSION)
            if not refcounts:
                raise Exception('no response')
            return refcounts

    async def GetRtc(self) -> [4]:
        async with lock:
            async def get_state(byte):
                data = byte
                if len(data) == 4:
                    self.board.query_reply_data[CB_GET_RTC] = -1
                if len(data) == 8:
                    year = int(data[0]) + int(data[1]) * 256
                    month = int(data[2])
                    day = int(data[3])
                    hour = int(data[4])
                    minute = int(data[5])
                    second = int(data[6])
                    week = int(data[7])
                    self.board.query_reply_data[CB_GET_RTC] = [year, month, day, hour, minute, second, week]
                else:
                    self.board.query_reply_data[CB_GET_RTC] = [0, 0, 0, 0, 0, 0, 0]
                # logger.debug("%s %s" % (self.board.query_reply_data[CB_GET_RTC], current_task().get_name()))

            self.board.command_dictionary.update({CB_GET_RTC: get_state})
            await self.board._send_sysex(CB_GET_RTC)
            refcounts = await self.wait(CB_GET_RTC, timeout=1)
            # logger.debug("%s %s" % (refcounts, current_task().get_name()))
            return refcounts

    async def SetIP(self, data):
        """
        Args:
            data:
        """
        async with lock:
            async def get_state(byte):
                self.board.query_reply_data[CB_SET_IP] = byte

            if isinstance(data, str):
                data = data.split('.')
                data = bytearray(map(lambda x: int(x), data))
            self.board.command_dictionary.update({CB_SET_IP: get_state})
            await self.board._send_sysex(CB_SET_IP, (bytes(data)))
            selfData = await self.wait(CB_SET_IP)
            if not selfData:
                raise Exception('no response')
            return selfData

    async def SetRtc(self, data):
        """
        Args:
            data:
        """
        async with lock:
            async def get_state(byte):
                self.board.query_reply_data[CB_SET_RTC] = True

            self.board.command_dictionary.update({CB_SET_RTC: get_state})
            selfData = await self.send_sysex_wait(CB_SET_RTC, data, timeout=3)
            if not selfData:
                raise Exception('no response')
            return selfData

    async def SetTraceVariablesList(self, idxs):
        """
        Args:
            idxs:
        """
        logger.debug('SetTraceVariablesList:%d' % len(idxs))
        logger.debug(idxs)
        buff = b""
        if idxs:
            # keep a copy of requested idx
            await self.cmd(CB_CLEAR_V)
            inx = 0
            for idx, iectype in idxs:
                inx += 1
                ids = ctypes.string_at(ctypes.pointer(ctypes.c_uint16(idx)), 2)
                buff += ids
                # if len(buff) > 60:
                #     await self.SET_TRACE_VARIABLETransaction(buff)
                #     buff = b''
                self.confnodesroot.ShowPLCProgress(status=('Setting Monitor Variable %d/%d' % (inx, len(idxs))),
                                                   progress=inx * 100 / len(idxs))
            if buff:
                await self.SET_TRACE_VARIABLETransaction(buff)
            await self.confnodesroot.HidePLCProgress()
        else:
            await self.cmd(CB_CLEAR_V)

    async def SET_TRACE_VARIABLETransaction(self, val):
        """
        Args:
            val:
                """
        async with lock:
            async def get_ret(byte):
                self.board.query_reply_data[CB_SET_V] = byte

            self.board.command_dictionary.update({CB_SET_V: get_ret})
            dat = (val)
            logger.debug("%d.  %d :%s" % (len(val), len(dat), dat))
            await self.board._send_sysex(CB_SET_V, dat)
            ret = await self.wait(CB_SET_V)
            return ret

    async def SetForceVariablesList(self, idxs):
        """Call ctype imported function to append these indexes to registered
        variables in PLC debugger

        Args:
            idxs:
        """
        buff = b""
        inx = 0
        for idx, iectype, force in idxs:
            inx += 1
            ids = ctypes.string_at(ctypes.pointer(ctypes.c_uint16(idx)), 2)
            if force is not None:
                c_type, unpack_func, pack_func = TypeTranslator.get(iectype, (None, None, None))
                forced_type_size = ctypes.sizeof(c_type) \
                    if iectype != "STRING" else len(force) + 1
                forced_type_size_str = bytes([forced_type_size, ])
                forcestr = ctypes.string_at(
                    ctypes.pointer(
                        pack_func(c_type, force)),
                    forced_type_size)
                buff += ids + forced_type_size_str + forcestr
            else:
                buff += ids + bytes([0, ])
        if buff:
            async with lock:
                async def get_ret(byte):
                    self.board.query_reply_data[CB_SET_FORCE] = (byte)

                self.board.command_dictionary.update({CB_SET_FORCE: get_ret})
                await self.board._send_sysex(CB_SET_FORCE, buff)
                ret = await self.wait(CB_SET_FORCE)
                return ret

        return None

    async def GetTraceVariables(self, Token):
        """
        Args:
            Token:
        """
        async with lock:

            async def get_v(byte):
                # logger.debug(byte)
                self.board.query_reply_data[CB_GET_V] = bytes(byte)

            self.board.command_dictionary.update({CB_GET_V: get_v})
            await self.board._send_sysex(CB_GET_V)
            strbuf = await self.wait(CB_GET_V)
            TraceVariables = []
            if strbuf is not None and len(strbuf) >= 4:
                size = len(strbuf) - 4
                tick = ctypes.create_string_buffer(strbuf[:4])
                tick = ctypes.cast(tick, ctypes.POINTER(ctypes.c_uint32)).contents
                if size > 0:
                    cbuff = ctypes.create_string_buffer(strbuf[4:])
                    buff = ctypes.cast(cbuff, ctypes.c_void_p)
                    TraceBuffer = ctypes.string_at(buff.value, size)
                    # Add traces
                    TraceVariables.append((tick.value, TraceBuffer))
            return self.PLCStatus, TraceVariables

    async def cmd(self, CMD, timeout=1):
        async with lock:
            async def get_v(byte):
                r = byte
                if len(r) in (2, 4):
                    r = int.from_bytes(r, byteorder='little')
                elif len(r) > 1:
                    r = r
                elif len(r) == 1:
                    r = r[0]
                self.board.query_reply_data[CMD] = r

            self.board.command_dictionary.update({CMD: get_v})
            await self.board._send_sysex(CMD)
            selfData = await self.wait(CMD, timeout)
            if selfData is None:
                raise Exception('no response')
        return selfData

    async def PostFile(self, src, dest_file_name):
        """
        Args:
            src:
            dest_file_name:
        """
        if not self.ip:
            return await self.xymodem(src, dest_file_name)
        if self.confnodesroot:
            self.confnodesroot.logger.info("transfer start.")
        async with lock:

            if self.confnodesroot:
                self.confnodesroot.logger.info("transfer begin.")
            self.inx = 0
            self.pktsize = os.path.getsize(src) / 1024

            def hook(packet):
                self.inx += 1
                if self.confnodesroot:
                    self.confnodesroot.ShowPLCProgress(status='Downloading',
                                                       progress=self.inx * 100 / self.pktsize)

            t = tftpy.TftpClient(self.ip, options={'blksize': 512})
            t.upload(dest_file_name, src, packethook=hook, timeout=2)
            if self.confnodesroot:
                self.confnodesroot.AppFrame.ProgressStatusBar.Hide()
                self.confnodesroot.logger.info("transfer end.")
            return True

    async def ftp_upload(self, src, dest_file_name):
        """
        Args:
            src:
            dest_file_name:
        """
        if self.confnodesroot:
            self.confnodesroot.logger.info("transfer start.")
        async with lock:

            async with aioftp.Client.context(self.ip, 21, "root", "root") as client:
                if self.confnodesroot:
                    self.confnodesroot.logger.info("transfer begin.")
                self.inx = 0
                self.pktsize = os.path.getsize(src) / 1024
                await client.upload(src, dest_file_name, write_into=True)
                if self.confnodesroot:
                    self.confnodesroot.AppFrame.ProgressStatusBar.Hide()
                    self.confnodesroot.logger.info("transfer end.")
                return True

    async def SetPlcFile(self, src):
        async with lock:
            async def get_v(byte):
                self.board.query_reply_data[CB_SET_PLC_FILE] = byte

            self.board.command_dictionary.update({CB_SET_PLC_FILE: get_v})
            await self.board._send_sysex(CB_SET_PLC_FILE, src.encode())
            selfData = await self.wait(CB_SET_PLC_FILE)
            return selfData == [CB_SET_PLC_FILE, 0, 0xf7]

    async def PurgeBlobs(self):
        """
        Args:
            self:
        """
        return None

    async def SeedBlob(self, val):
        """
        Args:
            self:
            val:
        """
        return None

    async def NewPLC(self, MD5, object_blob, extra_files):
        """
        Args:
            self:
            MD5:
            object_blob:
            extra_files:
        """
        return True

    async def version(self):
        """
        Args:
            self:
        """
        ver = await self.board.get_firmware_version()
        return ver

    async def send_sysex_wait(self, cmd, data=None, timeout=3):
        await self._send_sysex(cmd, data)
        return await self.wait(cmd, timeout)

    async def get_plc_state(self):
        """
        Args:
            self:
        """
        async with lock:
            async def get_state(byte):
                b = (byte)
                b = int.from_bytes(b, 'little')
                state = b
                self.board.query_reply_data[FM_GET_PLC_STATE] = state

            self.board.command_dictionary.update({FM_GET_PLC_STATE: get_state})
            await self.board._send_sysex(FM_GET_PLC_STATE)
            return await self.wait(FM_GET_PLC_STATE)

    async def get_task_nrs(self):
        """
        Args:
            self:
        """
        async with lock:
            async def nrs(byte):
                b = (byte)
                self.board.query_reply_data[FM_GET_TASK_NRS] = b[0]

            self.board.command_dictionary.update({FM_GET_TASK_NRS: nrs})
            await self.board._send_sysex(FM_GET_TASK_NRS, [])
            return await self.wait(FM_GET_TASK_NRS)

    async def get_task_detail(self, inx):
        """
        Args:
            self:
            inx:
        """
        async with lock:
            async def detail(byte):
                data = (byte)
                if len(data) != 24:
                    logger.error('data error')
                dat = {}
                r = int.from_bytes(data[0:3], 'little')

                class Flags_bits(ctypes.LittleEndianStructure):
                    _fields_ = [
                        ("type", ctypes.c_uint8, 2),
                        ("running", ctypes.c_uint8, 1),
                        ("overtime", ctypes.c_uint8, 1),
                    ]

                class Flags(ctypes.Union):
                    _fields_ = [("b", Flags_bits),
                                ("asbyte", ctypes.c_uint32)]

                flags = Flags()
                flags.asbyte = r
                dat['type'] = flags.b.type
                dat['running'] = flags.b.running
                dat['overtime'] = flags.b.overtime
                dat['count'] = int.from_bytes(data[4:7], 'little')
                dat['lrt'] = int.from_bytes(data[8:11], 'little')
                dat['rt_min'] = int.from_bytes(data[12:15], 'little')
                dat['rt_max'] = int.from_bytes(data[16:19], 'little')
                dat['diff'] = int.from_bytes(data[20:23], 'little')
                self.board.query_reply_data[FM_GET_TASK_DETAIL] = dat

            self.board.command_dictionary.update({FM_GET_TASK_DETAIL: detail})
            await self.board._send_sysex(FM_GET_TASK_DETAIL, [inx])
            res = await self.wait(FM_GET_TASK_DETAIL)
            return res

    async def get_task_name(self, inx):
        """
        Args:
            self:
            inx:
        """
        async with lock:
            async def get_name(byte):
                self.board.query_reply_data[FM_GET_TASK_NAME] = (byte)

            self.board.command_dictionary.update({FM_GET_TASK_NAME: get_name})
            await self.board._send_sysex(FM_GET_TASK_NAME, [inx])
            return await self.wait(FM_GET_TASK_NAME)

    async def get_children_nrs(self):
        """
        Args:
            self:
        """
        async with lock:
            async def nrs(byte):
                self.board.query_reply_data[FM_GET_TASK_NRS] = byte

            self.board.command_dictionary.update({FM_GET_TASK_NRS: nrs})
            await self.board._send_sysex(FM_GET_TASK_NRS, [])
            return await self.wait(FM_GET_TASK_NRS)

    def get_char(self, size, timeout: float = 1, debug=None):
        data = asyncio.run(self.board.serial_port.read(size, timeout=timeout))
        if isinstance(data, int):
            data = bytes([data, ])
        # print(data)
        return data

    def put_char(self, data, timeout=3, debug=None):
        size = asyncio.run(self.board.serial_port.write(data))
        self.inx += size
        if self.inx % 128 == 0:
            logger.debug('Downloading,%d / %d' % (self.inx, self.pktsize))
            if self.confnodesroot:
                self.confnodesroot.ShowPLCProgress(status=('Downloading'), progress=self.inx * 100 / self.pktsize)
        return size

    async def get_file(self, name: str, dest: str = None, queue: Queue = None):

        self.dat = b''
        async with lock:
            async def get_result(bt):
                logger.debug(bt)
                if (len(bt)) == 4:
                    r = int.from_bytes(bt, "little", signed=True)
                    self.board.query_reply_data[FM_GET_DATA_BLOCK] = r
                elif len(bt) > 8:
                    block = int.from_bytes(bt[:4], "little", signed=True)
                    if block == 0:
                        self.filesize = int.from_bytes(bt[4:8], "little", signed=True)
                        self.blocksize = int.from_bytes(bt[8:12], "little", signed=True)
                        self.blknrs = int((self.filesize + self.blocksize) / self.blocksize)
                        self.nextblock = 1
                        logger.debug(
                            "filesize:%d,blocksize:%d,blknrs:%d" % (self.filesize, self.blocksize, self.blknrs))
                    elif block == self.nextblock:
                        sz = int.from_bytes(bt[4:8], "little", signed=False)
                        logger.debug("block:%d,sz:%d" % (block, sz))
                        if sz == len(bt[8:]):
                            self.dat += bytes(bt[8:])
                            if block < self.blknrs:
                                self.nextblock += 1
                            else:
                                self.board.query_reply_data[FM_GET_DATA_BLOCK] = base64.encodebytes(self.dat).decode()
                                if queue:
                                    with open(dest, 'wb') as fd:
                                        fd.write(self.dat)
                                    await queue.put(None)
                                return
                        if queue:
                            await queue.put((block, self.blknrs))

                cmd = int.to_bytes(self.nextblock, 4, "little")
                await self._send_sysex(FM_GET_DATA_BLOCK, cmd)

            self.board.command_dictionary.update({FM_GET_DATA_BLOCK: get_result})
            cmd = int.to_bytes(0, 4, "little")  # block No.
            cmd += int.to_bytes(LFS, 4, "little")
            cmd += int.to_bytes(0, 4, "little")  # address
            cmd += int.to_bytes(0, 4, "little")  # size
            cmd += name.encode()
            cmd += bytes([0])
            if queue:
                res = await self._send_sysex(FM_GET_DATA_BLOCK, cmd)
            else:
                res = await self.send_sysex_wait(FM_GET_DATA_BLOCK, cmd, timeout=10)
            return res

    @trace_function()
    async def send_file(self, name: str, dest: str, mem: int, address: int, max_size: int, queue=None):
        logger.debug("send_file:%s,mem:%d,address:0x%x" % (name, mem, address))
        if self.confnodesroot:
            self.confnodesroot.logger.info("传送 %s 到 %s 起始地址 = 0x%x." % (name, MEM_REGION[mem], address))
        if queue:
            await queue.put(
                {'status': "传送 %s 到 %s 起始地址 = 0x%x." % (name, MEM_REGION[mem], address), 'block': 0, 'total': 0})
        async with lock:
            async def get_result(bt):
                value = int.from_bytes(bt, 'little', signed=True)
                self.board.query_reply_data[FM_PUT_DATA_BLOCK] = value

            self.board.command_dictionary.update({FM_PUT_DATA_BLOCK: get_result})
            fsize = os.path.getsize(name)
            if fsize > max_size:
                if self.confnodesroot:
                    self.confnodesroot.logger.info("传送失败,超过内存大小!")
                if queue:
                    await queue.put({'status': "传送失败,超过内存大小!", 'block': 0, 'total': fsize})
                return False
            if queue:
                await queue.put(
                    {'status': "传送 %s 到 %s 起始地址 = 0x%x." % (name, MEM_REGION[mem], address), 'block': 0,
                     'total': fsize})
            with open(name, 'rb') as fp:
                cmd = int.to_bytes(0, 4, "little")
                cmd += int.to_bytes(mem, 4, "little")
                cmd += int.to_bytes(address, 4, "little")
                cmd += int.to_bytes(fsize, 4, "little")
                if dest:
                    cmd += dest.encode()
                    cmd += bytes([0])
                trace.get_current_span().add_event('send_file block begin')
                bsize = await self.send_sysex_wait(FM_PUT_DATA_BLOCK, cmd, 5)
                trace.get_current_span().add_event('send_file block end')
                if not bsize or bsize < 0:
                    logger.error(bsize)
                    if self.confnodesroot:
                        self.confnodesroot.logger.write_error(
                            f"返回错误! 代码 = {bsize} :{error_code[bsize - (-1001)]}")
                    if queue:
                        await queue.put(
                            {'status': f"返回错误! 代码 = {bsize} :{error_code[bsize - (-1001)]}", 'block': 0,
                             'total': fsize})
                    return False
                block = 1
                size = 0
                while True:
                    cnx = fp.read(bsize - 8)
                    if cnx == b'': break
                    size += len(cnx)
                    cmd = int.to_bytes(block, 4, "little")
                    cmd += cnx
                    for i in range(5):
                        try:
                            logger.info("send block %d len=%d" % (block, len(cmd)))
                            if queue:
                                await queue.put({'status': "传送中", 'block': size, 'total': fsize})
                            bno = await self.send_sysex_wait(FM_PUT_DATA_BLOCK, cmd, timeout=3)
                            if bno == block:
                                block += 1
                                break
                            else:
                                logger.error("block %d size=%d result %d" % (block, size, bno))
                        except Exception:
                            logger.error(traceback.format_exc())
                        if i == 4:
                            cmd = int.to_bytes(-1, 4, "little", signed=True)
                            cmd += int.to_bytes(size, 4, "little")
                            await self.send_sysex_wait(FM_PUT_DATA_BLOCK, cmd, timeout=3)
                            raise Exception("error")
                    logger.info("send_file : %d / %d 0x%x 0x%x" % (size, fsize, cnx[0], cnx[1]))
                    if self.confnodesroot:
                        self.confnodesroot.ShowPLCProgress(status='Downloading',
                                                           progress=size * 100 / fsize)

            cmd = int.to_bytes(-1, 4, "little", signed=True)
            cmd += int.to_bytes(size, 4, "little")
            cmd += int.to_bytes(size, 4, "little")
            await self.send_sysex_wait(FM_PUT_DATA_BLOCK, cmd)
            if self.confnodesroot:
                self.confnodesroot.AppFrame.ProgressStatusBar.Hide()
                self.confnodesroot.logger.info("传送完成.")
            if queue:
                await queue.put({'status': "done", 'block': size, 'total': fsize})
            return True

    async def fm_read_loc(self, d1, d2, d3, typ, sz):
        error = 0
        returnVal = []
        async with lock:
            async def nrs(byte):
                d = (byte)
                if len(d) < 1:
                    self.board.query_reply_data[FM_GET_LOC_TAB] = False
                typ = d[0]
                v_size = d[1]
                proto = int.from_bytes(d[2:4], 'little', signed=False)
                a_size = int.from_bytes(d[4:6], 'little', signed=False)
                a_data = int.from_bytes(d[6:6 + a_size], 'little', signed=False)
                v_data = int.from_bytes(d[6 + a_size:6 + a_size + v_size], 'little', signed=False)
                self.board.query_reply_data[FM_GET_LOC_TAB] = {"type": typ, "v_size": v_size, "proto": proto,
                                                               "a_size": a_size,
                                                               "a_data": a_data, "v_data": v_data, }

            self.board.command_dictionary.update({FM_GET_LOC_TAB: nrs})
            b = int.to_bytes(d1, 1, "little", signed=False)
            b += int.to_bytes(d2, 1, "little", signed=False)
            b += int.to_bytes(d3, 1, "little", signed=False)
            b += int.to_bytes(d1, 1, "little", signed=False)
            dat = (b)
            res = await self.send_sysex_wait(FM_GET_LOC_TAB, dat)
            return res

    async def FM_GET_LOC_TAB(self, ch):
        error = 0
        returnVal = []
        async with lock:
            async def nrs(byte):
                d = (byte)
                if len(d) < 1:
                    self.board.query_reply_data[FM_GET_LOC_TAB] = False
                typ = d[0]
                v_size = d[1]
                proto = int.from_bytes(d[2:4], 'little', signed=False)
                a_size = int.from_bytes(d[4:6], 'little', signed=False)
                a_data = int.from_bytes(d[6:6 + a_size], 'little', signed=False)
                v_data = get_value_by_type(d[6 + a_size:6 + a_size + v_size], typ)
                self.board.query_reply_data[FM_GET_LOC_TAB] = {"type": typ, "v_size": v_size, "proto": proto,
                                                               "a_size": a_size,
                                                               "a_data": a_data, "v_data": v_data, }

            self.board.command_dictionary.update({FM_GET_LOC_TAB: nrs})
            b = int.to_bytes(ch, 4, "little", signed=False)
            dat = (b)
            res = await self.send_sysex_wait(FM_GET_LOC_TAB, dat)
            return res

    async def fm_get_var(self, ch):

        async with lock:
            async def nrs(byte):
                d = (byte)
                if len(d) < 4:
                    self.board.query_reply_data[FM_GET_DBG] = False
                index = int.from_bytes(d[0:4], 'little', signed=False)
                v_type = types[d[4]]
                v_size = d[5]
                flag = d[6]
                value = get_value_by_type(d[7:7 + v_size], v_type)
                force_value = get_value_by_type(d[7 + v_size:7 + v_size + v_size], v_type)
                self.board.query_reply_data[FM_GET_DBG] = {"v_size": v_size, "flag": flag, "value": value,
                                                           "v_type": v_type,
                                                           "force": flag & 0x2 == 0x2, "debug": flag & 0x1 == 0x1,
                                                           'index': index,
                                                           "retain": flag & 0x4 == 0x4, "output": flag & 0x8 == 0x8,
                                                           "force_value": force_value}

            self.board.command_dictionary.update({FM_GET_DBG: nrs})
            b = int.to_bytes(ch, 4, "little", signed=False)
            dat = (b)
            res = await self.send_sysex_wait(FM_GET_DBG, dat, timeout=3)
            return res

    async def fm_get_value(self, region, adr, typ, length, wait=False):
        async def nrs(byte):
            logger.debug(f"fm_get_value {byte}")
            try:
                d = (byte)
                if len(d) < 7:
                    self.board.query_reply_data[FM_READ_VALUE_REP] = False
                    return
                region = d[0]
                _typ = d[1]
                if _typ != typ:
                    logger.error(f"type error:{adr} {_typ} {typ} {length}")
                    self.board.query_reply_data[FM_READ_VALUE_REP] = False
                    return
                ch = int.from_bytes(d[2:6], 'little', signed=False)
                if ch != adr:
                    logger.error("adr error:%d l=%d" % (adr, length))
                    self.board.query_reply_data[FM_READ_VALUE_REP] = False
                    return
                value = get_value_by_type(d[6:], _typ)
                if not isinstance(value, (int, float)):
                    logger.error(f"value error:{value}")
                    self.board.query_reply_data[FM_READ_VALUE_REP] = False
                    return
                # logger.debug("%d tye=%d" % (value, _typ))

                self.board.query_reply_data[FM_READ_VALUE_REP] = [region, ch, _typ, value]
            except Exception as e:
                logger.error(traceback.format_exc())

        async with lock:
            self.board.command_dictionary.update({FM_READ_VALUE_REP: nrs})
            dat = int.to_bytes(region, 1, "little", signed=False)
            dat += int.to_bytes(adr, 4, "little", signed=False)
            dat += int.to_bytes(typ, 1, "little", signed=False)
            dat += int.to_bytes(length, 2, "little", signed=False)
            t1 = time.time()
            await self.board._send_sysex(FM_READ_VALUE, dat)
            res = await self.wait(FM_READ_VALUE_REP, timeout=2)
            t = time.time() - t1
            logger.debug(f"fm_get_value {t}")
            return res

    async def fm_set_value(self, region, adr, typ, length, value, wait=False):
        async def nrs(byte):
            d = (byte)
            if len(d) < length + 7:
                self.board.query_reply_data[FM_WRITE_VALUE_REP] = False
            self.board.query_reply_data[FM_WRITE_VALUE_REP] = True

        def get_bytes_from_value(value, typ):
            if typ == 0:
                return bytearray(struct.pack('f', value))
            elif typ == 1:
                return bytearray(struct.pack('lf', value))
            elif typ == 2:
                return int.to_bytes(value, 1, "little", signed=True)
            elif typ == 3:
                return int.to_bytes(value, 2, "little", signed=True)
            elif typ == 4:
                return int.to_bytes(value, 4, "little", signed=True)
            elif typ == 5:
                return int.to_bytes(value, 8, "little", signed=True)
            elif typ == 6:
                return int.to_bytes(value, 1, "little", signed=False)
            elif typ == 7:
                return int.to_bytes(value, 2, "little", signed=False)
            elif typ == 8:
                return int.to_bytes(value, 4, "little", signed=False)
            elif typ == 9:
                return int.to_bytes(value, 8, "little", signed=False)
            elif typ == 10:
                return int.to_bytes(value, 1, "little", signed=False)
            elif typ == 11:
                return int.to_bytes(value, 2, "little", signed=False)
            elif typ == 12:
                return int.to_bytes(value, 4, "little", signed=False)
            elif typ == 13:
                return int.to_bytes(value, 8, "little", signed=False)
            elif typ == 14:
                return int.to_bytes(value, 1, "little", signed=False)
            elif typ == 15:
                return bytes([value])
            elif typ == 16:
                return value.encode('utf-8')
            elif typ == 17:
                return value.encode('utf-8')
            elif typ == 18:
                return value.encode('utf-8')

        async with lock:
            self.board.command_dictionary.update({FM_WRITE_VALUE_REP: nrs})
            b = int.to_bytes(region, 1, "little", signed=False)
            b += int.to_bytes(adr, 4, "little", signed=False)
            b += int.to_bytes(length, 2, "little", signed=False)
            b += get_bytes_from_value(value, typ)
            dat = (b)
            await self.board._send_sysex(FM_WRITE_VALUE, dat)
            res = await self.wait(FM_WRITE_VALUE_REP)
            return res

    async def fm_get_plc_info(self):
        error = 0
        returnVal = []

        async def nrs(byte):
            r = (byte)
            build = int.from_bytes(r[:4], 'little', signed=False)
            sn = [chr(a) for a in r[4:]]
            name = ''.join(sn)
            self.board.query_reply_data[FM_GET_PLC_INFO] = {"build": build, "name": name}

        async with lock:
            self.board.command_dictionary.update({FM_GET_PLC_INFO: nrs})
            res = await self.send_sysex_wait(FM_GET_PLC_INFO)
            return res

    async def cb_thread_info(self):
        states = ['Running', 'Ready', 'Blocked', 'Suspended', 'Deleted', 'Invalid', 'error']

        async def nrs(byte):
            r = bytes(byte)
            pb = pb_thread_list()
            result = pb.ParseFromString(r)
            self.board.query_reply_data[CB_THREAD_INFO] = pb.thread

        async with lock:
            self.board.command_dictionary.update({CB_THREAD_INFO: nrs})
            res = await self.send_sysex_wait(CB_THREAD_INFO, timeout=2)
            return res

    async def report_analog(self, chs):
        self.result = []

        async def report(byt):
            self.result.append(byt)

        # logger.debug(self.result)

        async def extended_analog(byte):
            byt = list((byte[2:-1]))
            self.result.append([2, byte[1], int.from_bytes(byt, 'little', signed=True), time.time()])
            pin = byte[1]
            if pin < len(self.board.analog_pins):
                value = int.from_bytes(byt, 'little', signed=True)
                self.board.analog_pins[pin].current_value = value
                time_stamp = time.time()
                self.board.analog_pins[pin].event_time = time_stamp

        self.board.command_dictionary.update({PrivateConstants.EXTENDED_ANALOG: extended_analog})
        ana = copy(self.report_analog_pins)
        for ch in chs:
            self.board.analog_pins[ch].cb = report
            self.board.analog_pins[ch].differential = 0
            command = bytes([ch, PrivateConstants.REPORTING_ENABLE])
            b = (command)
            await self.board._send_sysex(PrivateConstants.REPORT_ANALOG, b)
            if ch not in self.report_analog_pins:
                self.report_analog_pins.append(ch)
            if ch in ana:
                ana.remove(ch)
            await asyncio.sleep(0.1)
        for ch in ana:
            self.board.analog_pins[ch].cb = report
            self.board.analog_pins[ch].differential = 0
            if ch < 16:
                command = [PrivateConstants.REPORT_ANALOG | ch, PrivateConstants.REPORTING_DISABLE]
                await self.board._send_command(command)
            else:
                command = bytes([ch, PrivateConstants.REPORTING_DISABLE])
                b = (command)
                await self.board._send_sysex(PrivateConstants.REPORT_ANALOG, b)
            if ch in self.report_analog_pins:
                self.report_analog_pins.remove(ch)
            await asyncio.sleep(0.1)

    async def loop_report_analog(self, chs):
        await self.report_analog(chs)
        while self.board:
            for b in self.result:
                yield b
                self.result.remove(b)
            await asyncio.sleep(0.1)

    async def rm_file(self, fn):
        async def get_result(byte):
            r = bytes(byte)
            r1 = int.from_bytes(r, "little", signed=True)
            self.board.query_reply_data[FM_REMOVE_FILE] = r1

        async with lock:
            self.board.command_dictionary.update({FM_REMOVE_FILE: get_result})
            cmd = fn.encode()
            cmd += bytes([0])
            await self.board._send_sysex(FM_REMOVE_FILE, cmd)
            res = await self.wait(FM_REMOVE_FILE)
            if res == "rm ok":
                return True
            return False

    async def cb_get_remain_mem(self):
        async def get_result(byte):
            r = bytes(byte)
            if len(r) == 2:
                value = int.from_bytes(r, 'little', signed=False)
            else:
                value = 0
            self.board.query_reply_data[CB_GET_REMAIN_MEM] = value
            # logger.debug(value)

        async with lock:
            self.board.command_dictionary.update({CB_GET_REMAIN_MEM: get_result})
            await self.board._send_sysex(CB_GET_REMAIN_MEM)
            res = await self.wait(CB_GET_REMAIN_MEM, timeout=1)
            # logger.debug(res)
            return res

    async def cb_get_boot_version(self):
        async def get_result(byte):
            r = byte
            if len(r) == 8:
                model_id = int.from_bytes(r[0:4], 'little', signed=False)
                build = int.from_bytes(r[4:8], 'little', signed=False)
                self.board.query_reply_data[CB_GET_BOOT_VERSION] = [str(hex(model_id)), build]
            else:
                self.board.query_reply_data[CB_GET_BOOT_VERSION] = None

        async with lock:
            self.board.command_dictionary.update({CB_GET_BOOT_VERSION: get_result})
            await self.board._send_sysex(CB_GET_BOOT_VERSION)
            res = await self.wait(CB_GET_BOOT_VERSION, timeout=1)
            logger.debug(res)
            return res

    async def cb_cpu_usage(self):
        async def get_result(byte):
            r = bytes(byte)
            value = int.from_bytes(r, 'little', signed=False)
            self.board.query_reply_data[CB_CPU_USAGE] = value

        async with lock:
            self.board.command_dictionary.update({CB_CPU_USAGE: get_result})
            await self.board._send_sysex(CB_CPU_USAGE)
            res = await self.wait(CB_CPU_USAGE, timeout=1)
            return res

    async def read_analog(self, analog_pin):
        async with lock:
            # state = await self.board.get_pin_state(analog_pin)
            val = await self.board.analog_read(analog_pin)
            return val[0]

    async def serial_config(self, ser_id, baud, format=6):
        async with lock:
            self.serial.update({ser_id: {'status': 0, 'msg': b''}})
            command = bytes([PrivateConstants.SERIAL_CONFIG | ser_id, ])
            command += baud.to_bytes(4, byteorder='little')
            command += format.to_bytes(1, byteorder='little')
            bn = (command)
            await self.board._send_sysex(PrivateConstants.SERIAL_MESSAGE, bn)

            async def report(byt):
                func = byt[1] & 0xf0
                rid = byt[1] & 0xf
                if func == PrivateConstants.SERIAL_REPLY:
                    self.serial[rid]["msg"] += bytes(two_byte_to_byte(byt[2:-1]))
                elif func == PrivateConstants.SERIAL_STATUS:
                    self.serial[rid].update({"status": byt[2]})
                    logger.debug(byt)

            self.board.command_dictionary.update({PrivateConstants.SERIAL_MESSAGE: report})
            command = [PrivateConstants.SERIAL_READ | ser_id, PrivateConstants.SERIAL_STOP_READING]
            await self.board._send_sysex(PrivateConstants.SERIAL_MESSAGE, bytes(command))
            await asyncio.sleep(0.1)
            command = [PrivateConstants.SERIAL_READ | ser_id, PrivateConstants.SERIAL_READ_CONTINUOUSLY]
            await self.board._send_sysex(PrivateConstants.SERIAL_MESSAGE, bytes(command))

    async def serial_close(self, ser_id):
        command = [PrivateConstants.SERIAL_READ | ser_id, PrivateConstants.SERIAL_STOP_READING]
        await self.board._send_sysex(PrivateConstants.SERIAL_MESSAGE, bytes(command))
        await asyncio.sleep(0.1)
        command = [PrivateConstants.SERIAL_CLOSE | ser_id, ]
        bn = (command)
        await self.board._send_sysex(PrivateConstants.SERIAL_MESSAGE, bn)

    async def serial_write(self, ser_id, data):
        command = bytes([PrivateConstants.SERIAL_WRITE | ser_id, ])
        command += bytes(data)
        await self.board._send_sysex(PrivateConstants.SERIAL_MESSAGE, command)

    serial = {}

    async def serial_read(self, ser_id, timeout=10):
        # logger.debug(time.time())
        t = time.time() + timeout
        while time.time() < t:
            if self.serial.get(ser_id) and self.serial[ser_id].get("msg"):
                vv = self.serial.get(ser_id)["msg"]
                self.serial[ser_id]["msg"] = b''
                return vv
            await asyncio.sleep(0.1)
        # logger.debug(time.time())
        return b''

    async def RemoveKey(self, key):
        async def get_state(byte):
            b = (byte)
            r = int.from_bytes(b, 'little', signed=True)
            self.board.query_reply_data[CB_RM_KEY] = r

        async with lock:
            self.board.command_dictionary.update({CB_RM_KEY: get_state})
            await self.board._send_sysex(CB_RM_KEY, key.encode() + bytes([0]))
            res = await self.wait(CB_RM_KEY)
            return res

    async def ReadKey(self, key):
        async def get_state(b):
            logger.debug(b)
            b = bytes(b)
            state = int.from_bytes(b[:2], 'little', signed=True)
            name = b[2:].split(b'\x00')[0].decode()
            value = b[2:].split(b'\x00')[1].decode()
            self.kv[name] = value
            self.board.query_reply_data[CB_READ_KEY] = [state, name, value]

        async with lock:
            self.board.command_dictionary.update({CB_READ_KEY: get_state})
            cmd = key.encode() + bytes([0])
            await self.board._send_sysex(CB_READ_KEY, cmd)
            res = await self.wait(CB_READ_KEY, timeout=30)
            return res

    async def WriteKey(self, key: str, data: str) -> bool:
        async def get_state(byte):
            b = bytes(byte)
            state = int.from_bytes(b[:2], 'little', signed=True)
            if state >= 0:
                name = b[2:].split(b'\x00')[0].decode()
                value = b[2:].split(b'\x00')[1].decode()
                self.kv[name] = value
                self.board.query_reply_data[CB_WRITE_KEY] = [state, name, value]
            else:
                self.board.query_reply_data[CB_WRITE_KEY] = [state, None, None]

        async with lock:
            self.board.command_dictionary.update({CB_WRITE_KEY: get_state})
            vv = key.encode() + bytes([0]) + data.encode() + bytes([0])
            await self.board._send_sysex(CB_WRITE_KEY, vv)
            res = await self.wait(CB_WRITE_KEY, timeout=30)
            return res

    async def ReadKeyBytes(self, key):
        async def get_state(byte):
            logger.debug(byte)
            b = bytes(byte)
            typ = int.from_bytes(b[0:4], byteorder='little', signed=False)
            val = b[4:]
            self.board.query_reply_data[FM_READ_KEY_BYTES] = {"type": typ, "value": val}

        async with lock:
            self.board.command_dictionary.update({FM_READ_KEY_BYTES: get_state})
            cmd = key.encode() + bytes([0])
            await self.board._send_sysex(FM_READ_KEY_BYTES, cmd)
            res = await self.wait(FM_READ_KEY_BYTES, timeout=30)
            return res

    async def WriteKeyBytes(self, key, data, Type: int) -> bool:
        async def get_state(byte):
            i = int.from_bytes(bytes(byte), byteorder='little', signed=True)
            self.board.query_reply_data[FM_WRITE_KEY_BYTES] = i

        async with lock:
            if Type == KV_BIN_VALUE:
                data = data.encode()
            elif Type == KV_STR_VALUE:
                data = data.encode() + bytes([0])
            elif Type == KV_KEY_UDINT:
                v = int(data)
                data = int.to_bytes(v, 4, byteorder='little', signed=False)
            self.board.command_dictionary.update({FM_WRITE_KEY_BYTES: get_state})
            vv = key.encode() + bytes([0]) + int.to_bytes(Type, 4, byteorder='little', signed=False) + data
            vv = (vv)
            await self.board._send_sysex(FM_WRITE_KEY_BYTES, vv)
            res = await self.wait(FM_WRITE_KEY_BYTES, timeout=30)
            return res

    async def GetTsl(self, db_index: int, inx: int) -> object:
        async def get_state(b):
            try:
                status = int.from_bytes(b[:2], byteorder='little', signed=True)
                tid = int.from_bytes(b[2:6], "little", signed=False)
                timstamp = int.from_bytes(b[6:10], "little", signed=False)
                state = int.from_bytes(b[10:14], "little")
                self.board.query_reply_data[CB_GET_TSL_BY_ID] = {"id": tid, "ts": timstamp, "state": status,
                                                                 "bytes": b[14:]}
            except Exception:
                logger.error(traceback.format_exc())
                self.board.query_reply_data[CB_GET_TSL_BY_ID] = False

        async with lock:
            self.board.command_dictionary.update({CB_GET_TSL_BY_ID: get_state})
            dat = int.to_bytes(db_index, 1, "little")
            dat += int.to_bytes(inx, 4, "little")
            await self.board._send_sysex(CB_GET_TSL_BY_ID, dat)
            res = await self.wait(CB_GET_TSL_BY_ID, 5)
            return res

    async def ClearTsl(self, db: int) -> bool:
        async def get_state(byte):
            tid = int.from_bytes(byte[:4], "little", signed=True)
            self.board.query_reply_data[CB_TSL_CLEAR] = tid == 0

        async with lock:
            self.board.command_dictionary.update({CB_TSL_CLEAR: get_state})
            dat = int.to_bytes(db, 1, "little")
            return await self.send_sysex_wait(CB_TSL_CLEAR, dat, timeout=60)

    async def get_key_list(self):
        self.keys = []

        async def extended_analog(byte):
            b = (byte)
            name = ''
            for a in b:
                if a == 0: break
                name += chr(a)
            Type = int.from_bytes(b[len(name) + 1:len(name) + 5], "little", signed=False)
            value = b[len(name) + 5:]
            if Type == 0:
                value = value
            elif Type == 1:
                value = [chr(a) for a in value]
                value = ''.join(value)
            elif Type == 2:
                value = int.from_bytes(value, "little", signed=False)
            self.keys.append({"key": name, "type": Type, "value": value})

        async with lock:
            self.board.command_dictionary.update({FM_LIST_KEY: extended_analog})
            await self.board._send_sysex(FM_LIST_KEY)

    async def loop_get_key_list(self):
        await self.get_key_list()
        while self.board:
            for b in self.keys:
                yield b
                self.keys.remove(b)
            await asyncio.sleep(0.1)

    async def SetTslRange(self, db_index: int, start: int, end: int, state: fdb_tsl_status) -> object:
        t1 = 0

        async def get_count(b):
            since = int.from_bytes(b[0:4], 'little', signed=False)
            last = int.from_bytes(b[4:8], 'little', signed=False)
            since_id = int.from_bytes(b[8:12], 'little', signed=False)
            last_id = int.from_bytes(b[12:16], 'little', signed=False)
            count = int.from_bytes(b[16:20], 'little', signed=True)
            self.board.query_reply_data[CB_SET_TSL_RANGE] = {'since': since, 'last': last, 'since_id': since_id,
                                                             'last_id': last_id, 'count': count}

        async with lock:
            self.board.command_dictionary.update({CB_SET_TSL_RANGE: get_count})
            dat = int.to_bytes(db_index, 1, 'little')
            dat += int.to_bytes(start, 4, "little")
            dat += int.to_bytes(end, 4, "little")
            dat += int.to_bytes(state, 4, "little")
            t1 = time.time()
            res = await self.send_sysex_wait(CB_SET_TSL_RANGE, dat, timeout=30)
            logger.debug(res)
            return res

    async def SetTslStatus(self, db_index: int, start: int, end: int, state: fdb_tsl_status) -> int:
        t1 = 0

        async def get_count(byte):
            b = (byte)
            result = int.from_bytes(b[0:4], 'little', signed=False)
            self.board.query_reply_data[CB_SET_TSL_STATUS] = result

        async with lock:
            self.board.command_dictionary.update({CB_SET_TSL_STATUS: get_count})
            dat = int.to_bytes(db_index, 1, 'little')
            dat += int.to_bytes(start, 4, "little")
            dat += int.to_bytes(end, 4, "little")
            dat += int.to_bytes(state, 4, "little")
            t1 = time.time()
            res = await self.send_sysex_wait(CB_SET_TSL_STATUS, dat, timeout=30)
            return res

    async def tslClear(self, db_index: int) -> bool:
        t1 = 0

        async def get_count(byte):
            run_time = time.time() - t1
            if run_time > 0.5:
                logger.warning("timeout:%f" % run_time)
            b = (byte)
            value = int.from_bytes(b, 'little', signed=True)
            self.board.query_reply_data[CB_TSL_CLEAR] = value

        async with lock:
            self.board.command_dictionary.update({CB_TSL_CLEAR: get_count})
            dat = int.to_bytes(db_index, 1, 'little')
            t1 = time.time()
            await self.board._send_sysex(CB_TSL_CLEAR, dat)
            res = await self.wait(CB_TSL_CLEAR)
            return res

    async def get_location(self, l1, l2, l3, l4):
        async def get_result(byte):
            b = (byte)
            if len(b) == 4:
                [value] = struct.unpack('f', bytes(b))
            else:
                value = int.from_bytes(b, 'little', signed=True)
            self.board.query_reply_data[FM_GET_LOCATION] = value

        async with lock:
            self.board.command_dictionary.update({FM_GET_LOCATION: get_result})
            dat = int.to_bytes(l1, 1, 'little')
            dat += int.to_bytes(l2, 1, 'little')
            dat += int.to_bytes(l3, 1, 'little')
            dat += int.to_bytes(l4, 1, 'little')
            dat = (dat)
            res = await self.send_sysex_wait(FM_GET_LOCATION, dat)
            return res

    async def set_location(self, l1, l2, l3, l4, val):
        async def get_result(byte):
            b = (byte)
            value = int.from_bytes(b, 'little', signed=True)
            self.board.query_reply_data[FM_SET_LOCATION] = value

        async with lock:
            self.board.command_dictionary.update({FM_SET_LOCATION: get_result})
            dat = int.to_bytes(l1, 1, 'little')
            dat += int.to_bytes(l2, 1, 'little')
            dat += int.to_bytes(l3, 1, 'little')
            dat += int.to_bytes(l4, 1, 'little')
            v = int.to_bytes(val, 2, 'little')
            dat += int.to_bytes(len(v), 1, 'little')
            dat += v
            dat = (dat)
            res = await self.send_sysex_wait(FM_SET_LOCATION, dat)
            return res

    async def read_plc_var_loop(self, var_list):
        var = ['xa32:1']
        count = 0
        while self.plc_var_reading:
            for i in var_list:
                # if i[5] not in var: continue
                var_name = i[5]
                region = i[1]
                addr = i[3]
                try:
                    with async_timeout.timeout(0.3):
                        await varLock.acquire()
                except asyncio.exceptions.TimeoutError:
                    pass

                self.plc_vars.update({region: {addr: var_name}})
                try:
                    val = await self.fm_get_value(region, addr, i[2], i[4])
                    if val:
                        [region, ch, _typ, value] = val
                        logger.debug("%s , %s %s =%s" % (var_name, region, addr, value))
                        rg = self.plc_vars.get(region)
                        if not rg:
                            logger.error("region error:%d" % region)
                            self.board.query_reply_data[FM_READ_VALUE_REP] = False
                            return
                        self.plc_var.update(
                            {var_name: {'name': var_name, 'value': value, 'region': region, 'address': addr,
                                        'type': _typ}})
                        self.plc_var_changed = True
                        varLock.release()
                        reportLock.set()
                        count = 0
                except ConnectionResetError:
                    self.plc_var_reading = False
                    raise ConnectionResetError
                except OSError:
                    self.plc_var_reading = False
                    raise OSError
                except Exception as e:
                    logger.error(traceback.format_exc())
                    count += 1
                    if count > 5:
                        self.plc_var_reading = False
                        raise Exception("read plc var error")

    async def loop_plc_var(self, plc_var_list):
        self.plc_var_reading = True
        self.board.loop.create_task(self.read_plc_var_loop(plc_var_list), name="read_plc_var")
        while self.plc_var_reading:
            await reportLock.wait()
            reportLock.clear()
            yield self.plc_var
            self.plc_var_changed = False
            # await asyncio.sleep(0.00001)

    async def stop_loop_plc_var(self):
        self.plc_var_reading = False

    async def fm_lfs_ls(self, path, since=0, sz=10):
        async def get_result(byte):
            fi = pb_filelist()
            logger.debug(" ".join("{:02x}".format(c) for c in byte))
            b = bytes(byte)
            s = fi.ParseFromString(b)
            ls = []
            for l in fi.file:
                ls.append(
                    {'name': l.filename, 'size': l.filesize, "index": l.index, 'dir': l.dir, "time": l.filetime})
            self.board.query_reply_data[FM_LFS_LS] = ls

        async with lock:
            self.board.command_dictionary.update({FM_LFS_LS: get_result})
            dat = int.to_bytes(since, 4, 'little')
            dat += int.to_bytes(sz, 4, 'little')
            dat += path.encode()
            dat += bytes([0])
            res = await self.send_sysex_wait(FM_LFS_LS, dat, timeout=10)
            return res

    async def fm_get_serial_info(self, inx):
        async def get_result(byte):
            serial = pb_serial()
            s = serial.ParseFromString(bytes(byte))
            self.board.query_reply_data[FM_GET_SERIAL_INFO] = serial

        async with lock:
            self.board.command_dictionary.update({FM_GET_SERIAL_INFO: get_result})
            dat = int.to_bytes(inx, 1, 'little')
            res = await self.send_sysex_wait(FM_GET_SERIAL_INFO, dat)
            return res

    async def proto_cmd(self, cmd: cmd) -> pb_response:
        async def get_result(byte):
            self.board.query_reply_data[FM_PROTOBUF] = byte

        async with lock:
            self.board.query_reply_data[FM_PROTOBUF] = None
            # logger.debug("fm_protobuf")
            self.board.command_dictionary.update({FM_PROTOBUF: get_result})
            byte = await self.send_sysex_wait(FM_PROTOBUF, cmd.SerializeToString(), timeout=3)
            respose = pb_response()
            respose.ParseFromString(bytes(byte))
            sub = respose.object.WhichOneof("object")
            if sub:
                obj = getattr(respose.object, sub)
                self.region.update({obj.DESCRIPTOR.name: {"class": obj}})
                obj = MessageToDict(obj, including_default_value_fields=True, preserving_proto_field_name=True)
                setattr(self, sub, obj)
                if sub == 'rte_const':
                    self.model = obj.get('model')
                # if sub == 'info':
                #     sn = obj['sn'].decode()
                #     obj['sn'] = sn
                return obj
            return respose

    async def fm_protobuf(self, cmd, param=0) -> dict:
        async def get_result(byte):
            self.board.query_reply_data[FM_PROTOBUF] = byte

        async with lock:
            self.board.query_reply_data[FM_PROTOBUF] = None
            # logger.debug("fm_protobuf")
            self.board.command_dictionary.update({FM_PROTOBUF: get_result})
            _cmd = pb_cmd()
            _cmd.cmd = cmd
            _cmd.param = param
            byte = await self.send_sysex_wait(FM_PROTOBUF, _cmd.SerializeToString(), timeout=5)
            # object = parse_protobuf_message(bytes(byte), "pmsg")
            Obj = pb_object()
            s = Obj.ParseFromString(bytes(byte))
            sub = Obj.WhichOneof("object")
            if sub:
                obj = getattr(Obj, sub)
                self.region.update({obj.DESCRIPTOR.name: {"class": obj, "index": param}})
                obj = MessageToDict(obj, including_default_value_fields=True, preserving_proto_field_name=True)
                setattr(self, sub, obj)
                if sub == 'rte_const':
                    self.model = obj.get('model')
                if sub == 'rte_info':
                    obj['sn'] = ''.join(['%02X' % i for i in base64.decodebytes(obj['sn'].encode())])
                return obj
            return getattr(self, "rte_const", None)

    # async def fm_flat(self, cmd, param=0):
    #     async def get_result(byte):
    #         self.board.query_reply_data[FM_PROTOBUF] = byte
    #
    #     async with lock:
    #         self.board.query_reply_data[FM_PROTOBUF] = None
    #
    #         self.board.command_dictionary.update({FM_PROTOBUF: get_result})
    #         _cmd = pb_cmd()
    #         _cmd.cmd = cmd
    #         _cmd.param = param
    #         byte = await self.send_sysex_wait(FM_PROTOBUF, _cmd.SerializeToString())
    #         fbb = flatbuffers.Builder(len(byte))
    #         fbb.Bytes[0:len(byte)] = bytes(byte)
    #         message = flatbuffers.Table(bytes(byte), fbb.Head())
    #         message.Init(fbb.Bytes, fbb.Pos)
    #         fields = message.fields
    #         for field in fields:
    #             print(field)

    async def fm_module(self, cmd, param=0):
        async def get_result(byte):
            # logger.debug(param)
            # logger.debug(byte)

            self.board.query_reply_data[FM_PROTOBUF] = byte

        async with lock:
            # logger.debug("fm_module")
            self.board.query_reply_data[FM_PROTOBUF] = None

            self.board.command_dictionary.update({FM_PROTOBUF: get_result})
            _cmd = pb_cmd()
            _cmd.cmd = cmd
            _cmd.param = param
            byte = await self.send_sysex_wait(FM_PROTOBUF, _cmd.SerializeToString(), timeout=3)
            obj = pb_object()
            obj.ParseFromString(bytes(byte))
            # logger.debug(obj)
            sub = obj.WhichOneof("object")
            if not sub:
                logger.error("fm_module error")
            # logger.debug(sub)
            m = {}
            if sub:
                obj = getattr(obj, sub)
                self.region.update({sub: {"class": obj, "index": param}})
                # obj = MessageToDict(obj, including_default_value_fields=True, preserving_proto_field_name=True)
                data = []
                for field_name, field_descriptor in obj.DESCRIPTOR.fields_by_name.items():
                    field_type = field_descriptor.type
                    value = getattr(obj, field_name, field_descriptor.default_value)
                    if field_type == field_descriptor.TYPE_ENUM:
                        value = field_descriptor.enum_type.values_by_number[value].name
                    if isinstance(value, bytes):
                        value = ''.join(['%02X' % i for i in value])
                    data.append({"name": field_name, "typ": get_protobuf_field_type_name(field_type), "value": value})
                m = {'name': sub, 'data': data, 'type': obj.DESCRIPTOR.name}
            return m

    async def fm_rte_data(self):
        if not self.model or not self.rte_const:
            logger.error("fm_var_data error")
            return

        async def get_result(byte):
            self.board.query_reply_data[FM_PROTOBUF] = byte
            # logger.debug(byte)

        async with lock:
            logger.debug("fm_var_data")
            self.board.command_dictionary.update({FM_PROTOBUF: get_result})
            _cmd = pb_cmd()
            _cmd.cmd = pb_cmd_type.read_rte_data
            byte = await self.send_sysex_wait(FM_PROTOBUF, _cmd.SerializeToString(), timeout=3)
            obj = pb_var(self.model, self.rte_const.get('major'), self.rte_const.get('minor'),
                         self.rte_const.get('build'))
            data = []
            if obj:
                obj.ParseFromString(bytes(byte))
                for field_name, field_descriptor in obj.DESCRIPTOR.fields_by_name.items():
                    field_type = field_descriptor.type
                    value = getattr(obj, field_name, field_descriptor.default_value)
                    if field_type == field_descriptor.TYPE_ENUM:
                        value = field_descriptor.enum_type.values_by_number[value].name
                    if isinstance(value, bytes):
                        value = value.decode()
                    data.append({"name": field_name, "typ": get_protobuf_field_type_name(field_type), "value": value})
                m = {'name': obj.DESCRIPTOR.name, 'data': data, 'type': data[0]['value']}
                return m
            return data

    async def fm_holder_data(self):
        if not self.model or not self.rte_const:
            logger.error("fm_var_data error")
            return

        async def get_result(byte):
            self.board.query_reply_data[FM_PROTOBUF] = byte

        async with lock:
            # logger.debug("fm_holder_data")
            self.board.query_reply_data[FM_PROTOBUF] = None

            self.board.command_dictionary.update({FM_PROTOBUF: get_result})
            _cmd = pb_cmd()
            _cmd.cmd = pb_cmd_type.read_rte_holder
            byte = await self.send_sysex_wait(FM_PROTOBUF, _cmd.SerializeToString(), timeout=3)
            obj = pb_holder(self.model, self.rte_const.get('major'), self.rte_const.get('minor'),
                            self.rte_const.get('build'))
            if obj:
                obj.ParseFromString(bytes(byte))
                data = []
                for field_name, field_descriptor in obj.DESCRIPTOR.fields_by_name.items():
                    field_type = field_descriptor.type
                    value = getattr(obj, field_name, field_descriptor.default_value)
                    if field_type == field_descriptor.TYPE_ENUM:
                        value = field_descriptor.enum_type.values_by_number[value].name
                    if isinstance(value, bytes):
                        value = value.decode()
                    data.append({"name": field_name, "typ": get_protobuf_field_type_name(field_type), "value": value})
                m = {'name': obj.DESCRIPTOR.name, 'data': data, 'type': data[0]['value']}
                return m
            return None

    async def setVariable(self, region: str, varName: str, value: str, index: int = 0):
        async def get_result(byte):
            self.board.query_reply_data[FM_PROTOBUF] = byte

        async with lock:
            self.board.query_reply_data[FM_PROTOBUF] = None

            self.board.command_dictionary.update({FM_PROTOBUF: get_result})
            cmd = pb_cmd()
            cmd.cmd = pb_cmd_type.write_module
            cls = self.region.get(region)
            if cls is None:
                logger.error("region error:%s" % region)
                return False
            cmd.param = cls.get('index')
            cmd.index = index
            cmd.tag, field_type = get_tag_by_field_name(cls.get('class'), varName)
            if cmd.tag is None:
                return False
            cmd.data = encode_protobuf_data(field_type, value)
            byte = await self.send_sysex_wait(FM_PROTOBUF, cmd.SerializeToString(), timeout=3)
            object = pb_object()
            s = object.ParseFromString(bytes(byte))
            sub = object.WhichOneof("object")
            obj = getattr(object, sub)
            # obj = MessageToDict(obj, including_default_value_fields=True, preserving_proto_field_name=True)
            if sub == 'info':
                obj['tick'] = int(obj['tick'] / 1000)
                # sn = obj['sn'].decode()
                # obj['sn'] = sn
            if sub == region:
                return True
            return False

    async def setData(self, varName: str, value: str, index: int = 0):
        async def get_result(byte):
            self.board.query_reply_data[FM_PROTOBUF] = byte

        async with lock:
            self.board.query_reply_data[FM_PROTOBUF] = None

            self.board.command_dictionary.update({FM_PROTOBUF: get_result})
            cmd = pb_cmd()
            cmd.cmd = pb_cmd_type.write_rte_data
            cls = pb_var(self.model, self.rte_const.get('major'), self.rte_const.get('minor'),
                         self.rte_const.get('build'))
            if cls is None:
                return False
            cmd.index = index
            cmd.tag, field_type = get_tag_by_field_name(cls, varName)
            if cmd.tag is None:
                return False
            cmd.data = encode_protobuf_data(field_type, value)
            byte = await self.send_sysex_wait(FM_PROTOBUF, cmd.SerializeToString(), timeout=3)
            object = cls()
            s = object.ParseFromString(bytes(byte))
            return True

    async def setHolder(self, varName: str, value: str, index: int = 0):
        async def get_result(byte):
            self.board.query_reply_data[FM_PROTOBUF] = byte

        async with lock:
            self.board.query_reply_data[FM_PROTOBUF] = None

            self.board.command_dictionary.update({FM_PROTOBUF: get_result})
            cmd = pb_cmd()
            cmd.cmd = pb_cmd_type.write_rte_holder
            cls = pb_holder(self.model, self.rte_const.get('major'), self.rte_const.get('minor'),
                            self.rte_const.get('build'))
            if cls is None:
                return False
            cmd.index = index
            cmd.tag, field_type = get_tag_by_field_name(cls, varName)
            if cmd.tag is None:
                return False
            cmd.data = encode_protobuf_data(field_type, value)
            byte = await self.send_sysex_wait(FM_PROTOBUF, cmd.SerializeToString(), timeout=3)
            object = cls
            s = object.ParseFromString(bytes(byte))
            return object

    async def protobuf(self, object):
        async def get_result(byte):
            self.board.query_reply_data[FM_PROTOBUF] = byte

        async with lock:
            self.board.query_reply_data[FM_PROTOBUF] = None

            self.board.command_dictionary.update({FM_PROTOBUF: get_result})
            if isinstance(object, str):
                object = object.encode()
            byte = await self.send_sysex_wait(FM_PROTOBUF, object, timeout=3)
            return base64.encodebytes(bytes(byte)).decode()

    async def GetSDOEntriesData(self, Entries, slave):
        '''读取单个SDO变量'''
        for item in Entries:
            cmd = pb_soem_cmd()
            cmd.cmd = pb_soem_cmd_type.soem_sdo_upload
            cmd.slave_id = slave + 1
            cmd.index = int(item['idx'], 16)
            cmd.sub_index = int(item['subIdx'], 16)
            cmd.size = int(item['size'])
            res = await self.soem_cmd(cmd)
            item['value'] = res['value']
        return Entries

    slaves = {}

    async def SetSDOTraceValues(self, SDOMonitorEntries, SlavePos):
        self.slaves[SlavePos] = SDOMonitorEntries

    async def GetSDOData(self) -> []:
        '''读取全部SDO变量'''
        data = []
        for slave in self.slaves:
            info = self.slaves[slave]
            data += await self.GetSDOEntriesData(info, slave)
        return data

    async def soem_cmd(self, cmd: pb_soem_cmd) -> pb_response:
        async def get_result(byte):
            self.board.query_reply_data[FM_SOEM] = byte

        async with lock:
            self.board.query_reply_data[FM_SOEM] = None
            # logger.debug("fm_protobuf")
            self.board.command_dictionary.update({FM_SOEM: get_result})
            byte = await self.send_sysex_wait(FM_SOEM, cmd.SerializeToString(), timeout=5)
            respose = pb_response()
            respose.ParseFromString(bytes(byte))
            return respose


if __name__ == '__main__':
    loop = asyncio.new_event_loop()
    f = FIRMATA(None, 'COM4', evt_loop=loop, dtr=1, rts=0, arduino_wait=10)
    f.board.use_sn = False
    loop.run_until_complete(f.init())
    # loop.run_until_complete(
    #     f.send_file("D:\\testCenter\\python_src\\firmware\\boot_kb12822.0.285.bin", "rte.bin", PROG_FLASH, 0x0))
    # loop.run_until_complete(f.serial_config(4, 115200))
    while True:
        # loop.run_until_complete(f.serial_write(4, [1, 2, 3, 4, 55, 6, 7, 8, 9]))
        # time.sleep(0.1)
        # v = loop.run_until_complete(f.serial_read(4))
        # if v:
        #     logger.info(v)
        try:
            val = loop.run_until_complete(f.fm_get_value(8, 4 * 16, 0, 4, True))
            logger.info(val)
            # time.sleep(0.1)
        except Exception as e:
            logger.error(str(e))
